package org.gicentre.tweet;


import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.gicentre.apps.hide.TreemapState;
import org.gicentre.apps.hide.TreemapStateGui;
import org.gicentre.hive.Expression;
import org.gicentre.hive.Hive;
import org.gicentre.utils.gui.TextInput;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.rosaloves.bitlyj.Url;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PFont;
import processing.core.PImage;

import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.http.AccessToken;
import twitter4j.http.RequestToken;

/**Handles sending the Tweets to Twitter and a server at giCentre (so it can cope
 * with long tweets)
 * 
 * Also provides a GUI. Does no event handing itself. Must call draw() and keypressed()
 * when the appropriate events are fired. The GUI handing itself being activated/not
 * activated, so draw() and keyPressed() should be called everytime. Keypressed should
 * be called before over keyboard handling from the main sketch. It will reset the key
 * variable if it interprets it.
 * 
 * Requires an application key (need to register it with twitter and they've give you one)
 * Handles authorising user accounts and storing this locally so it can be used between sessions
 * 
 * Sends tweet asynchronously.
 * 
 * @author Aidan Slingsby, giCentre
 *
 */
public class Tweeter extends Thread implements ClipboardOwner{

	private List<Tweet> tweets;
	private Twitter twitter;
	private String applicationToken;
	private String applicationSecret;
	AccessToken accessToken;
	private AccessToken prevAccessToken;
	private RequestToken prevRequestToken;
	private String twitterAuthMessage=null;
	Clipboard clipboard;
	private TextInput textInput;
	private boolean isActive=false;
	private PApplet applet;
	private float fontSize;
	private PFont font;
	private char activateKey=':';
	private char changeUserKey='@';
	private char defaultUserKey='~';
	private boolean allowChangeOfAccount;
	private int mode=0; //0=tweet mode; 1=auth mode; 2=auth result mode
	private TreemapStateGui treemapStateGui;
	private String hashtag="unspecifiedDataset";
	private ByteArrayOutputStream currentTweetImage;
	private String twitterName=null;
	Tweetable tweetable;
	private String bitlyUser,bitlyKey;
	
	
	

	/**Constructor
	 * 
	 * @param applet The main processing sketch
	 * @param font
	 * @param fontSize
	 * @param hive An object that implements the HiVE interface (thus provides a state)
	 * @param applicationTokenString The application key
	 * @param applicationSecretString the application secret
	 */
	public Tweeter(PApplet applet,PFont font,float fontSize, TreemapStateGui treemapStateGui,String applicationTokenString,String applicationSecretString,Tweetable tweetable,String bitlyUser,String bitlyKey) {
		System.setProperty ("twitter4j.loggerFactory","twitter4j.internal.logging.NullLoggerFactory");
		tweets=new LinkedList<Tweet>();
		clipboard=Toolkit.getDefaultToolkit().getSystemClipboard();
		this.applicationToken=applicationTokenString;
		this.applicationSecret=applicationSecretString;
		this.start();
		this.textInput = new TextInput(applet,font,fontSize);
		this.applet=applet;
		this.fontSize=fontSize;
		this.font=font;
		this.treemapStateGui=treemapStateGui;
		this.tweetable=tweetable;
		Properties properties = new Properties();
		try{
			properties.load(applet.createInput("twitter.ini"));
			String accessTokenString = properties.getProperty("accessToken");
			String accessSecretString = properties.getProperty("accessSecret");
			setAccessToken(accessTokenString, accessSecretString);
		}
		catch(Exception e){
			//fail silently
		}
		this.bitlyUser=bitlyUser;
		this.bitlyKey=bitlyKey;
	}

	/**Set the hashtag (usually a keyword that identifies the dataset
	 * 
	 * @param hashtag
	 */
	public void setHashtag(String hashtag){
		if (hashtag==null){
			hashtag="unspecifiedDataset";
		}
		this.hashtag=hashtag.replace(" ", "");
		if (!hashtag.startsWith("#")){
			this.hashtag="#"+hashtag;
		}
	}

	/**Sets whether the user should be able to change their account
	 * 
	 * @param allowChangeOfAccount
	 */
	public void allowChangeOfAccount(boolean allowChangeOfAccount){
		this.allowChangeOfAccount=allowChangeOfAccount;
	}

	/**Sets an access token to log a twitter user in - see the Twitter4j documentation.
	 * 
	 * @param accessTokenString
	 * @param accessSecretString
	 */
	public void setAccessToken(String accessTokenString,String accessSecretString){
		accessToken=new AccessToken(accessTokenString,accessSecretString);
		twitter=new TwitterFactory().getOAuthAuthorizedInstance(applicationToken,applicationSecret,accessToken);
		prevAccessToken=accessToken;
		try {
			twitterName=twitter.getScreenName();
		}
		catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (TwitterException e) {
			e.printStackTrace();
		}

	}
	
	
	public AccessToken getAccessToken(){
		return accessToken;
	}
	
	public void run() {
		//perpetual loop
		while (true){

			//send any unsent tweets
			if (!tweets.isEmpty()){
				//encode logEvent details as part of a URL
				Tweet tweetInfo;
				synchronized (tweets) {

					tweetInfo = tweets.remove(0);
					
					System.out.print("Sending tweet...");
					
					if (hashtag==null){
						hashtag="";					}
					String hashtags="#vizTweet "+hashtag;
					String id=null;
					try{
						ClientHttpRequest clientHttpRequest = new ClientHttpRequest("http://vega.soi.city.ac.uk/~sbbb717/vizTweet/storeTweet.php");
						clientHttpRequest.setParameter("hive", tweetInfo.getHive());
						clientHttpRequest.setParameter("comment", tweetInfo.getComment());
						clientHttpRequest.setParameter("hashtags", hashtags);
						clientHttpRequest.setParameter("twitterUserId", tweetInfo.getTwitterUserId());
						InputStream imageInputStream = new ByteArrayInputStream(tweetInfo.getOutputStream().toByteArray());
						clientHttpRequest.setParameter("png", "dsdsd",imageInputStream);
						InputStream is=clientHttpRequest.post();
						BufferedReader br = new BufferedReader(new InputStreamReader(is));
						id=br.readLine();
						while (br.ready()){
							System.out.println(br.readLine());
						}
					}
					catch(IOException e){
						System.out.println(e);
						tweets.add(0,tweetInfo);
						break;
					}
					if (id!=null){
						String twitterTweet=hashtags+" "+tweetInfo.getComment()+" "+tweetInfo.getHive();
						
						//get shortened url
						String shortUrl=null;
						try{
							shortUrl=com.rosaloves.bitlyj.Bitly.as(bitlyUser,bitlyKey).call(com.rosaloves.bitlyj.Bitly.shorten("http://gicentre.org/vizTweet?id="+id)).getShortUrl();
						}
						catch (Exception e){
							System.err.println(e);
							tweets.add(0,tweetInfo);
							break;
						}

						try{
							twitterTweet=URLDecoder.decode(twitterTweet,"UTF-8");
						}
						catch (UnsupportedEncodingException e) {
							System.err.println(e);
						}
						int tweetTruncLen = 139-shortUrl.length();
						if (tweetTruncLen<twitterTweet.length()){
							twitterTweet=twitterTweet.substring(0,tweetTruncLen-3)+"... "+shortUrl;
						}
						else{
							twitterTweet=twitterTweet+" "+shortUrl;
						}

						try{
							twitter.updateStatus(twitterTweet);
						}
						catch (TwitterException e){
							System.err.println(e.getMessage());
						}
					}
					System.out.println("done.");
				}

			}

			
			else{
				//else pause the thread
				try {
					synchronized (this) {						
						this.wait();
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	/** Adds a tweet to the send queue
	 * 
	 * @param logEvent The event to log 
	 */
	public void add(Tweet tweetInfo){
		synchronized (tweets) {
			if (tweets.size()<10){
				tweets.add(tweetInfo);
			}
		}
		//resume the thread
		synchronized (this) {
			this.notify();
		}
	}
	
	/**Draw the gui
	 * Always call this. It handles activation on its own and won't draw if not active
	 * 
	 */
	public void draw(){
		applet.pushStyle();
		applet.resetMatrix();


		if (isActive){

			//background
			applet.fill(255,150);
			applet.rect(0,0,applet.width,applet.height);
			
			if (mode==0){
				applet.fill(100);
				applet.textFont(font);
				applet.textSize(fontSize);
				applet.textAlign(PApplet.CENTER,PApplet.CENTER);
				applet.text("Enter your tweet @"+twitterName+":",applet.width/2,applet.height/2-fontSize*2);
				textInput.draw(applet.width/2-applet.textWidth(textInput.getText())/2,applet.height/2);
				applet.fill(150);
				if (allowChangeOfAccount){
					applet.text("Press '"+activateKey+"' to cancel.\n'"+changeUserKey+"' to switch twitter account.\n'"+defaultUserKey+"' to set this as default Twitter account.",applet.width/2,applet.height/2+fontSize*3);
				}
				else{
					applet.text("Press '"+activateKey+"' to cancel.",applet.width/2,applet.height/2+fontSize*3);
				}
			}
			else if (mode==1){
				applet.fill(100);
				applet.textFont(font);
				applet.textSize(fontSize);
				applet.textAlign(PApplet.CENTER,PApplet.CENTER);
				applet.text("You need to authorise access from your Twitter account\n\nAn authorisation URL has been copied to the clipboard. Please paste this into your browser, follow the instructions, copy the PIN to your clipboard and paste it here with CTRL-V.",50,0,applet.width-100,applet.height);
				applet.fill(150);
			}
			else if (mode==2){
				applet.fill(100);
				applet.textFont(font);
				applet.textSize(fontSize);
				applet.textAlign(PApplet.CENTER,PApplet.CENTER);
				applet.text(twitterAuthMessage+"\n\nPress any key to continue",0,0,applet.width,applet.height);
				applet.fill(150);
			}
		}
		applet.popStyle();
	}
	
	/**Gets the key used to activate it
	 * 
	 * @return
	 */
	public char getActivateKey(){
		return activateKey;
	}

	/**Sets the key used to activate it
	 * 
	 * @param key
	 */
	public void setActivateKey(char key){
		this.activateKey=key;
	}

	/**Find out whether it's active
	 * 
	 * @return
	 */
	public boolean isActive(){
		return isActive;
	}
	
	/**Set whether it's active
	 * 
	 * @param isActive
	 */
    public void setIsActive(boolean isActive){
        this.isActive=isActive;
        if (this.isActive){
        	captureScreenshot();
        	mode=0;
        	textInput.setText("");
        	if (twitterName==null){
        		setToChangeUserMode();
        	}
        }
}

	/** Change user
	 * 
	 */
	private void setToChangeUserMode(){
		mode=1;
		try{
			twitter = new TwitterFactory().getOAuthAuthorizedInstance(applicationToken, applicationSecret);
			prevRequestToken = twitter.getOAuthRequestToken();
			clipboard.setContents(new StringSelection(prevRequestToken.getAuthenticationURL()),this);
		}
		catch (TwitterException e){
			
		}
		applet.loop();
	}
	
	/**Should always call when the a keyPressed event is fired
	 * 
	 * Handles activating itself and changing user account
	 */
	public void keyPressed() {
				
		
		//activate or deactivate
		if (applet.key==activateKey){
			this.setIsActive(!isActive);
			applet.loop();
			applet.key=0;
			return;
		}

		if (this.isActive){
			//change user
			if (allowChangeOfAccount && applet.key==changeUserKey){
				setToChangeUserMode();
				applet.key=0;
				return;
			}
			else if (allowChangeOfAccount && applet.key==defaultUserKey){
				try{
					Properties properties = new Properties();
					properties.setProperty("accessToken",accessToken.getToken());
					properties.setProperty("accessSecret",accessToken.getTokenSecret());
					properties.store(applet.createOutput("twitter.ini"), "");
					applet.key=0;
				}
				catch(Exception e){
					PApplet.println(e);
					//fail silently
				}
			}

			else if ((applet.keyEvent.isControlDown() || applet.keyEvent.isMetaDown())&& (applet.key=='v' || applet.key=='V' || applet.keyCode+32=='v'|| applet.keyCode+32=='V')){
				try{
					accessToken = twitter.getOAuthAccessToken((String)clipboard.getContents(this).getTransferData(DataFlavor.stringFlavor));
					twitterName=accessToken.getScreenName();
					prevAccessToken=accessToken;
					textInput.setText("");
					applet.key=8;
					mode=0;
				}
				catch(Exception e){
					twitterAuthMessage="Invalid PIN.";
					//repair the damage (set back user before
					twitter=new TwitterFactory().getOAuthAuthorizedInstance(applicationToken,applicationSecret,prevAccessToken);
					mode=2;
				}				
				
			}
			
			if (applet.key==PApplet.ENTER || applet.key==PApplet.RETURN){
				String hive;
				hive=Expression.formatExpression(this.treemapStateGui.getState());
				this.add(new Tweet(hive,textInput.getText(),currentTweetImage,twitterName));
				this.isActive=false;
			}
			else{
				textInput.keyPressed();
			}
			applet.loop();
			applet.key=0;
			return;
		}
	}

    /**Captures a screenshot, within the specified bounds
     * 
     */
    public void captureScreenshot(){
    	PImage image=tweetable.getImageToTweet();
    	ByteArrayOutputStream out = new ByteArrayOutputStream();
    	try {
    		ImageIO.write((BufferedImage)image.getImage(), "PNG", out);
    	} catch (IOException e) {
    		// TODO Auto-generated catch block
    		e.printStackTrace();
    	}
    	currentTweetImage=out;
    }
	
	public void lostOwnership(Clipboard clipboard, Transferable contents) {
		// TODO Auto-generated method stub
		
	}

	
}
